package pub.tbc.toolkit.bean;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author tbc on 2016/11/4 12:36:09.
 */
@Slf4j
public final class BeanHelper {
    private BeanHelper() {

    }

    /**
     * 将标准javaBean转为Map<String, Object>
     *
     * @param bean 要转换的javaBean
     * @return map
     */
    public static Map<String, Object> toMap(Object bean) {
        return toMap(bean, false);
    }


    public static Map<String, Object> toMap(Object bean, boolean copyNull) {
        return toMap(bean, copyNull, (String) null);
    }

    /**
     * 将标准javaBean转为Map<String, Object>
     *
     * @param bean     要转换的javaBean
     * @param copyNull 是否将值为null的属性也转到Map中
     * @return Map
     * @update 2016年11月07日 14:19:59 - 增加一个过滤列表，转换时排队指定字段
     * @update 2016年11月07日 16:37:52 - 增加对getClass()方法的自动过滤，不需要显式传过滤参数
     */
    public static Map<String, Object> toMap(Object bean, boolean copyNull, String... filterFields) {
        Set<String> filterFieldList = Sets.newHashSet(filterFields);
        // 自动过滤
        filterFieldList.add("getClass");
        Map<String, Object> ret = new HashMap<>();
        try {
            // getMethods: 所有公用（public）方法包括其继承类的公用方法，当然也包括它所实现接口的方法。
            // 包括公共、保护、默认（包）访问和私有方法，但【【【不包括继承的方法】】】。当然也包括它所实现接口的方法。
//            Method[] methods = bean.getClass().getDeclaredMethods();
            Method[] methods = bean.getClass().getMethods();
            for (Method method : methods) {
                if (method.getName().startsWith("get")) {
                    String field = method.getName();
                    field = field.substring(field.indexOf("get") + 3);
                    field = field.toLowerCase().charAt(0) + field.substring(1);
                    Object value = method.invoke(bean, (Object[]) null);
                    if (null == value && !copyNull)
                        continue;
                    // 过滤不参与转换的属性
                    if (filterFieldList.contains(method.getName()))
                        continue;
                    ret.put(field, value);
                }
            }
        } catch (InvocationTargetException | IllegalAccessException e) {
            log.error("");
        }
        return ret;
    }

    public static <T> T toBean(Map<String, Object> map, T t) {
        BeanInfo beanInfo = null;
        try {
            beanInfo = Introspector.getBeanInfo(t.getClass());
        } catch (IntrospectionException e) {
            log.error("获取BeanInfo对象失败：{}", e.getMessage());
            throw new RuntimeException(e.getMessage(), e);
        }
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();

        for (PropertyDescriptor property : propertyDescriptors) {
            String key = property.getName();
            if (map.containsKey(key)) {
                Object value = map.get(key);
                // 得到property对应的setter方法
                Method setter = property.getWriteMethod();
                try {
                    setter.invoke(t, value);
                } catch (IllegalAccessException | InvocationTargetException e) {
                    log.error("设置属性值失败：{}", e.getMessage());
                    throw new RuntimeException(e.getMessage(), e);
                }
            }
        }
        return t;

    }

    public static <T> T transMap2Bean(Map<String, Object> map, T t) {
        if (map == null || t == null) {
            log.warn("要转换的map为空，或者要转换的对象为空，直接返回null");
            return null;
        }
        try {
            BeanUtils.populate(t, map);
        } catch (Exception e) {
            log.error("设置属性值失败：{}", e.getMessage());
            throw new RuntimeException(e.getMessage(), e);
        }
        return t;
    }

    public static String toString(Object bean) {
        return toMap(bean).toString();
    }
}
